#include "mmu.h"
#include "assert.h"
#include "mstring.h"

/**
 * function [ mmu_table_alloc_tg4k ] implementation
 * @prototype: mx_void_pt mmu_table_alloc_tg4k (void);
 *      @param: void
 *      @return: start address of mmu table
 * @desc: allocate a mmu table with 4k granule size, memory size is 4kB
 */
extern mx_void_pt mmu_table_alloc_tg4k (void);

/**
 * map 4k (granule size is 4k)
 * @param table: level0 table address
 * @param va: virtual address
 * @param pa: physic address
 * @param isdev: is device memory type
 * @param multi: multiples of 4kB
 * @return: void
 */
void mmu_map_4k (mx_uint64_pt table, mx_void_pt va, mx_void_pt pa, mx_bool_t isdev, mx_usize_t multi)
{
    assert_true(((mx_uint64_t)table & (MMU_ALIGN_4K_MASK)) == 0);
    assert_true(((mx_uint64_t)va & (MMU_ALIGN_4K_MASK)) == 0);
    assert_true(((mx_uint64_t)pa & (MMU_ALIGN_4K_MASK)) == 0);

    // TODO: 检查 multi * 4k 后是否超出地址最大值，此处需要考虑 0xffffxxxxxxxxxxxx(ttbr1) 和 0x0000xxxxxxxxxxxx(ttbr0) 两种情况

    mx_uint64_t paddr = (mx_uint64_t)pa;
    mx_uint64_t vaddr = (mx_uint64_t)va;
    mx_uint64_t attr = isdev ? 1 : 0;
    mx_uint64_t off_masks[] = {
        MMU_OFFSET_TG4K_L1_MASK,
        MMU_OFFSET_TG4K_L2_MASK,
        MMU_OFFSET_TG4K_L3_MASK
    };
    mx_uint64_pt pnext;

    for (mx_usize_t i = 0; i < multi;
         i++, vaddr += (1 << MMU_ALIGN_4K), paddr += (1 << MMU_ALIGN_4K))
    {
        /* level0/1/2 table descriptor entry assignment */
        pnext = &table[GETMSKS((mx_uint64_t)vaddr, MMU_OFFSET_TG4K_L0_MASK)];
        for (mx_usize_t j = 0; j < 3; j++)
        {
            if (GETMSKS(*pnext, MMU_TABLE_ENTRY_VALID_MASK) == 1)
            {
                mx_uint64_t adr = GETMSK(*pnext, MMU_NTADDR_TG4K_MASK);
                pnext = &((mx_uint64_pt)adr)[GETMSKS(vaddr, off_masks[j])];
            }
            else
            {
                mx_void_pt addr = mmu_table_alloc_tg4k();
                assert_true((addr != mx_null) && (((mx_uint64_t)addr & MMU_ALIGN_4K_MASK) == 0));
                mx_memset(addr, 0, 0x1000); // TODO: 也许只需要设置页表的标志位即可，设置为无效
                *pnext = MMU_TABLED_ENTRY_TG4K(1, 0, 0, 0, (mx_uint64_t)addr);
                pnext = &((mx_uint64_pt)addr)[GETMSKS(vaddr, off_masks[j])];
            }
        }
        /* level3 block entry assignment */
        *pnext = MMU_BLOCK_ENTRY_TG4K_L3(0, 0, 0, 1, 2, 0, 0, attr, paddr);
    }
}
